package com.fowo.api.sys.service.impl;

import com.fowo.api.sys.mapper.SysRoleData2Mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fowo.api.common.annotaion.DataObjectType;
import com.fowo.api.common.model.RException;
import com.fowo.api.common.util.ContextHolder;
import com.fowo.api.common.util.ListUtils;
import com.fowo.api.config.AppProperties;
import com.fowo.api.sys.annotation.DataActivity;
import com.fowo.api.sys.entity.SysUser;
import com.fowo.api.sys.entity.SysUserPost;
import com.fowo.api.sys.entity.SysUserRole;
import com.fowo.api.sys.entity.SysUserSetting;
import com.fowo.api.sys.entity.enums.SysDataActivityAction;
import com.fowo.api.sys.mapper.*;
import com.fowo.api.sys.model.*;
import com.fowo.api.sys.service.SysRoleService;
import com.fowo.api.sys.service.SysMessageService;
import com.fowo.api.sys.service.SysUserService;
import com.fowo.api.user.model.CurrentUser;
import com.fowo.api.user.model.CurrentUserExtension;
import com.fowo.api.user.model.JwtUserInfo;
import org.apache.commons.collections4.CollectionUtils;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.Serializable;

import java.time.Duration;
import java.util.*;

@DataObjectType(SysUser.class)
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
    @Resource
    private SysRoleData2Mapper roleData2Mapper;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private SysRoleService roleService;
    @Resource
    private SysUserRoleMapper userRoleMapper;
    @Resource
    private SysUserPostMapper sysUserPostMapper;
    @Resource
    private SysDepartmentMapper sysDepartmentMapper;
    @Resource
    private SysMessageService sysMessageService;
    @Resource
    private SysUserSettingMapper sysUserSettingMapper;
    @Resource
    private AppProperties appProperties;

    @DataActivity(action = SysDataActivityAction.Add)
    @Transactional
    @Override
    public SysUser create(CreateSysUser model) throws RException {
        if(getByUsername(model.getUsername()) != null) {
            throw new RException("此用户名已经存在");
        }
        if(StringUtils.hasText(model.getMobile()) && getByMobile(model.getMobile()) != null) {
            throw new RException("此手机号已经存在");
        }
        SysUser user = new SysUser();
        BeanUtils.copyProperties(model, user);
        if (model.getPassword() != null) {
            user.setPassword(passwordEncoder.encode(model.getPassword()));
        }
        user.doCreate();
        if(save(user)) {
            return user;
        }
        return null;
    }

    @Override
    public SysUser getByUsername(String username) {
        return getOne(Wrappers.<SysUser>query().eq("username", username));
    }

    @Override
    public SysUser getByMobile(String mobile) {
        return getOne(Wrappers.<SysUser>query().eq("mobile", mobile));
    }

    @Override
    public CurrentUser getCurrentUserById(Long id) {
        final CurrentUser currentUser = getBaseMapper().selectCurrentUserById(id);
        if(currentUser != null) {
            currentUser.setAccess(ListUtils.join(",", getAllAuthorityByUserId(id, null)));
            currentUser.setUnreadCount(sysMessageService.getUnreadCount(Long.valueOf(currentUser.getUserid())));
            fillCurrentUserExtension(currentUser);
        }
        return currentUser;
    }

    private void fillCurrentUserExtension(CurrentUser currentUser) {
        Map<String, CurrentUserExtension> extensionMap = ContextHolder.getApplicationContext().getBeansOfType(CurrentUserExtension.class);
        for (CurrentUserExtension extension : extensionMap.values()) {
            extension.fill(currentUser);
        }
    }

    @Override
    public List<String> getAllAuthorityByUserId(Long id, Long appId) {
        List<SysAllAuthorityItem> sysAllAuthorityItems = getBaseMapper().selectAllAuthorityByUserId(id, appId);
        Set<String> allAuthorities = new HashSet<>();
        for (SysAllAuthorityItem sysAllAuthorityItem : sysAllAuthorityItems) {
            allAuthorities.add(sysAllAuthorityItem.getAuthority());
            if (StringUtils.hasText(sysAllAuthorityItem.getOtherAuthorities())) {
                Collections.addAll(allAuthorities, sysAllAuthorityItem.getOtherAuthorities().split(","));
            }
        }
        allAuthorities.addAll(roleData2Mapper.selectVisibleFieldAuthByUserId(id));
        return new ArrayList<>(allAuthorities);
    }
    
    @Override
    public List<SysUserItemVo> search(SysUserSearch search) {
        search.initSort("su.create_time desc", "su.");
        List<SysUserItemVo> list = getBaseMapper().search(search);
        roleService.fillUserListRoles(list);
        return list;
    }

    @Override
    public Page<SysUserItemVo> searchPage(SysUserSearch search) {
        search.initSort("su.create_time desc", "su.");
        Page<SysUserItemVo> page = getBaseMapper().searchPage(search.toPage(), search);
        roleService.fillUserListRoles(page.getRecords());
        return page;
    }

    @DataActivity(action = SysDataActivityAction.Modified)
    @Override
    public boolean resetPassword(Long id, ResetPassword resetPassword) {
        SysUser sysUser = new SysUser();
        sysUser.setId(id);
        sysUser.setPassword(passwordEncoder.encode(resetPassword.getPassword()));
        return getBaseMapper().updateById(sysUser) > 0;
    }

    @DataActivity(action = SysDataActivityAction.View)
    @Override
    public SysUserVo getVoById(Long id) {
        SysUser sysUser = getById(id);
        if (sysUser != null) {
            SysUserVo sysUserVo = new SysUserVo();
            BeanUtils.copyProperties(sysUser, sysUserVo);
            sysUserVo.setRoles(userRoleMapper.selectIdsByUserId(id));
            sysUserVo.setPostsIds(sysUserPostMapper.selectPostIdsByUserId(id));
            sysUserVo.setDepartmentIds(sysDepartmentMapper.getDepartmentIdsByUserId(id));
            return sysUserVo;
        }
        return null;
    }

    @DataActivity(action = SysDataActivityAction.Modified)
    @Transactional
    @Override
    public boolean update(SysUserVo sysUserVo) {
        if(StringUtils.hasText(sysUserVo.getPassword())) {
            sysUserVo.setPassword(passwordEncoder.encode(sysUserVo.getPassword()));
        }
        sysUserVo.doUpdate();
        if(baseMapper.updateById(sysUserVo) > 0) {
            if (sysUserVo.getRoles() != null) {
                updateRolesByUserId(sysUserVo.getId(), sysUserVo.getRoles());
            }
            if (sysUserVo.getPostsIds() != null) {
                updatePostsByUserId(sysUserVo.getId(), sysUserVo.getPostsIds());
            }
            if (sysUserVo.getDepartmentIds() != null) {
                updateDepartmentsByUserId(sysUserVo.getId(), sysUserVo.getDepartmentIds());
            }
            return true;
        }
        return false;
    }

    @DataActivity(action = SysDataActivityAction.Modified)
    @Override
    public boolean updateDisabled(Long id, Boolean isDisabled) {
        SysUser sysUser = new SysUser();
        sysUser.setId(id);
        sysUser.setIsDisabled(isDisabled);
        return baseMapper.updateById(sysUser) > 0;
    }

    @Override
    public boolean checkPassword(Long userId, String password) {
        final SysUser sysUser = getById(userId);
        if (sysUser == null) {
            return false;
        }
        return passwordEncoder.matches(password, sysUser.getPassword());
    }

    @Override
    public void changePassword(Long userId, ChangePassword changePassword) {
        final SysUser sysUser = getById(userId);
        if (sysUser == null) {
            throw new RException("未找到此用户");
        }
        if (!passwordEncoder.matches(changePassword.getPassword(), sysUser.getPassword())) {
            throw new RException("原密码错误");
        }
        SysUser updateSysUser = new SysUser();
        updateSysUser.setId(userId);
        updateSysUser.setPassword(passwordEncoder.encode(changePassword.getNewPassword()));
        updateById(updateSysUser);
    }

    @Override
    public boolean updateSetting(Long userId, String key, UserSetting setting) {
        SysUserSetting sysUserSetting = new SysUserSetting();
        sysUserSetting.setUserId(userId);
        sysUserSetting.setKey(key);
        sysUserSetting.setDataId(setting.getDataId());
        sysUserSetting.setValue(setting.getValue());
        return sysUserSettingMapper.updateValueByUserIdAndKey(sysUserSetting) > 0;
    }

    @Override
    public UserSetting getSetting(Long userId, String key) {
        return sysUserSettingMapper.getValueByUserIdAndKey(userId, key);
    }


    private void updateDepartmentsByUserId(Long userId, List<Long> departmentIds) {
        List<Long> oldDepartmentIds = sysDepartmentMapper.getDepartmentIdsByUserId(userId);
        if (CollectionUtils.isEqualCollection(oldDepartmentIds, departmentIds)) {
            // 没有变更，可直接返回
            return;
        }
        sysDepartmentMapper.deleteByUserId(userId);
        for(Long departmentId : departmentIds) {
            sysDepartmentMapper.insertUserDepartment(userId, departmentId);
        }
    }

    private void updatePostsByUserId(Long userId, List<Long> postsIds) {
        List<Long> oldPostsIds = sysUserPostMapper.selectPostIdsByUserId(userId);
        if (CollectionUtils.isEqualCollection(oldPostsIds, postsIds)) {
            // 没有变更，可直接返回
            return;
        }
        sysUserPostMapper.deleteByUserId(userId);
        for(Long postId : postsIds) {
            SysUserPost sysUserPost = new SysUserPost();
            sysUserPost.setUserId(userId);
            sysUserPost.setPostId(postId);
            sysUserPostMapper.insert(sysUserPost);
        }
    }

    private void updateRolesByUserId(Long userId, List<Long> roles) {
        if(roles == null) {
            return;
        }
        userRoleMapper.delete(Wrappers.<SysUserRole>query().eq("user_id", userId));
        for (Long roleId : roles) {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(userId);
            sysUserRole.setRoleId(roleId);
            sysUserRole.doCreate();
            userRoleMapper.insert(sysUserRole);
        }
    }

    @DataActivity(action = SysDataActivityAction.Del)
    @Override
    public boolean removeById(Serializable id) {
        SysUser sysUser = getById(id);
        if(sysUser != null) {
            if (sysUser.isProtected()) {
                throw new RException("被保护的数据不能删除");
            }
            JwtUserInfo jwtUserInfo = JwtUserInfo.fromHeader();
            if (jwtUserInfo != null && jwtUserInfo.getUserId() == id) {
                throw new RException("不能删除当前使用帐号");
            }
        }
        return super.removeById(id);
    }

    @Override
    public List<Long> getUserIdListBy(
        @Nullable
        List<Long> deptIdList, 
        @Nullable
        List<String> deptCodeList, 
        @Nullable
        List<Long> deepDeptIdList, 
        @Nullable
        List<String> deepDeptCodeList, 
        @Nullable
        List<Long> roleIdList,
        @Nullable
        List<Long> menuIdList
        ) {
            return baseMapper.selectUserIdListBy(deptIdList, deptCodeList, deepDeptIdList, deepDeptCodeList, roleIdList, menuIdList);
        }

    @Override
    public void updateUserLoginFail(SysUser user) {
        Date now = new Date();
        SysUser updateUser = new SysUser();
        updateUser.setId(user.getId());
        updateUser.setLastLoginFailTime(now);
        // 更新连续失败次数
        if (user.getLoginFailCount() == null || user.getLastLoginFailTime() == null) {
            updateUser.setLoginFailCount(1);
        } else {
            Date maxFailCountTime = datePlus(user.getLastLoginFailTime(), appProperties.getLoginFailCountInterval());
            if (maxFailCountTime.before(now)) {
                updateUser.setLoginFailCount(1);
            } else {
                updateUser.setLoginFailCount(user.getLoginFailCount() + 1);
            }
        }
        // 是否更新解锁时间
        if (updateUser.getLoginFailCount() >= appProperties.getLoginFailLockCount()) {
            updateUser.setLoginFailUnlockTime(datePlus(now, appProperties.getLoginFailLockDuration()));
        }

        updateById(updateUser);
    }

    @Override
    public List<SysUser> queryUserList(List<Long> userIds) {
        if (CollectionUtils.isEmpty(userIds)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(SysUser::getId, userIds);
        return list(queryWrapper);
    }

    private Date datePlus(Date date, Duration duration) {
        long millis = date.getTime() + duration.toMillis();
        return new Date(millis);
    }
}
